/* Copyright 2009 Anthony Surma
This file is part of Java's Kademlia Extension (JKADX).

JKADX is free software: myUniqueId can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.

JKADX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with JKADX.  If not, see <http://www.gnu.org/licenses/>.
 */

package jkadx.core;

import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import jkadx.dht.*;

final public class RPC
{
    final public static int             PING_FLAG               = 0x00000001;
    final public static int             FIND_NODE_FLAG          = 0x00000002;
    final public static int             FIND_VALUE_FLAG         = 0x00000004;
    final public static int             STORE_FLAG              = 0x00000008;
    final public static int             SOURCE_ADDRESS_OFFSET   = 1;
    final static int             SOURCE_PORT_OFFSET      = 5;
    final static int             SOURCE_ID_OFFSET        = 7;
    final public static int             ID_OFFSET               = 27;
    final public static int             HEADER_LENGTH           = 47; // bytes[] 0 -- 46
    final static int             FIND_NODE_TARGET_OFFSET = HEADER_LENGTH;
    final static byte[]          EMPTY_BYTE              = new byte[0];
    final static long            TIMEOUT_NS              = 300000000L; // 300 ms
    final static int             FLAGS_BIT_MASK          = 0x0000000F;
    final public static int             APPLICATION_BIT_MASK          = 0x00000080;
    final public static int             HASHCOLLISIONFINDER           = 0x00000080;
    final public static int             MPRIMEFINDER                  = 0x000000C0;
    final        CountDownLatch  cdl;
    final        Contact         contact;
  
    final        UniqueId        target; 
    final        AtomicInteger   totalAdded;
    final        AtomicInteger   totalReceived;
    final        ConcurrentHashMap<RPC, Object> rpcSentHashMap;
    final        UniqueId        rpcId;
    final        ConcurrentSkipListSet<Contact>  sortedList;
   
    public RPC(CountDownLatch cdl, Contact contact)
    {
       this.cdl              = cdl;
       this.contact          = contact;
       this.sortedList = null;
       this.target           = null;
       this.rpcSentHashMap   = null;
       this.rpcId            = null;
       this.totalAdded       = null;
       this.totalReceived    = null;
    }

    public RPC(CountDownLatch cdl, Contact contact, ConcurrentSkipListSet<Contact> sortedList, UniqueId target,
            ConcurrentHashMap<RPC, Object> rpcSentHashMap, UniqueId rpcId, AtomicInteger totalAdded, AtomicInteger totalReceived)
    {       
       this.cdl              = cdl;
       this.contact          = contact;
       this.sortedList = sortedList;
       this.target           = target;
       this.rpcSentHashMap   = rpcSentHashMap;
       this.rpcId            = rpcId;
       this.totalAdded       = totalAdded;
       this.totalReceived    = totalReceived;
    }

    void gotPingReply()
    {
        this.cdl.countDown();
    }

    void gotFindNodeReply(byte[] payload, int length, UniqueId myUniqueId)
    {        
        UniqueId[] closerUniqueIds = new UniqueId[KBucket.K]; // assume K number of contacts in payload
        int j = -1;
        int i;
        int testnumcontacts = 0;

        if (rpcSentHashMap.remove(this) != null) // each sent find node has its own corresponding entry in this hashmap
        {
            //System.out.println("Received findNodeReply and remove corresponding rpc from rpcsenthashmap.");
        }
        else
        {
            System.out.println("Received findNodeReply, but rpc was not found?!  Should never see this.");
        }



        for (int test = RPC.HEADER_LENGTH + Contact.ID_OFFSET; test < length; test += Contact.BYTE_LENGTH)
        {
            ++testnumcontacts;
        }
        //System.out.println("receivedFindNodeReply... num contacts in payload: " + testnumcontacts);
        
        for (i = RPC.HEADER_LENGTH + Contact.ID_OFFSET; i < length; i += Contact.BYTE_LENGTH)
        {              
            closerUniqueIds[++j] = new UniqueId(payload, i);
        }
               
        for (i -= (Contact.BYTE_LENGTH + Contact.ID_OFFSET); j >= 0; i -= Contact.BYTE_LENGTH)
        {        
            if (!closerUniqueIds[j].matches(myUniqueId))
            {               
                if (sortedList.add(new Contact(payload, closerUniqueIds[j], i))) // returns false if it was already in there
                {
                    totalAdded.incrementAndGet();
                }
            }
            --j;
        }
        totalReceived.incrementAndGet();
        cdl.countDown();        
    }
}